package com.xhdd.util;

import com.xhdd.util.global.AdjoinCode;
import com.xhdd.util.global.Cell;
import com.xhdd.util.global.CodesNexus;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.xhdd.constant.Const.*;

@Component
public class CodeUtil {


    @Resource
    WKTReader wktReader;

    /*获取4邻域编码*/
    public AdjoinCode get4adjoin(String code) {
        AdjoinCode adjoinCode = new AdjoinCode();
        int level = code.length();
        Cell cell = code2Kdj(code);
        double step = getStep(level);
//        邻格
        if (code.endsWith(ONE)) {
            adjoinCode.setRtCode(code.substring(0, level - 1) + TWO);
            adjoinCode.setDnCode(code.substring(0, level - 1) + THREE);
            /*上*/
            if (cell.getY() + step < maxL) {
                double x = cell.getX();
                double y = cell.getY() + step;
                StringBuilder upCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, upCode, level);
                adjoinCode.setUpCode(String.valueOf(upCode));
            }
            /*左*/
            if (cell.getX() - step > minL) {
                double x = cell.getX() - step;
                double y = cell.getY();
                StringBuilder lfCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, lfCode, level);
                adjoinCode.setLfCode(String.valueOf(lfCode));
            }
        } else if (code.endsWith(TWO)) {
            adjoinCode.setLfCode(code.substring(0, level - 1) + ONE);
            adjoinCode.setDnCode(code.substring(0, level - 1) + FOUR);
            /*上*/
            if (cell.getY() + step < maxL) {
                double x = cell.getX();
                double y = cell.getY() + step;
                StringBuilder upCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, upCode, level);
                adjoinCode.setUpCode(String.valueOf(upCode));
            }
            /*右*/
            if (cell.getX() + step < maxL) {
                double x = cell.getX() + step;
                double y = cell.getY();
                StringBuilder rtCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, rtCode, level);
                adjoinCode.setRtCode(String.valueOf(rtCode));
            }
        } else if (code.endsWith(THREE)) {
            adjoinCode.setUpCode(code.substring(0, level - 1) + ONE);
            adjoinCode.setRtCode(code.substring(0, level - 1) + FOUR);
            /*左*/
            if (cell.getX() - step > minL) {
                double x = cell.getX() - step;
                double y = cell.getY();
                StringBuilder lfCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, lfCode, level);
                adjoinCode.setLfCode(String.valueOf(lfCode));
            }
            /*下*/
            if (cell.getY() - step > minL) {
                double x = cell.getX();
                double y = cell.getY() - step;
                StringBuilder dnCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, dnCode, level);
                adjoinCode.setDnCode(String.valueOf(dnCode));
            }
        } else if (code.endsWith(FOUR)) {
            adjoinCode.setLfCode(code.substring(0, level - 1) + THREE);
            adjoinCode.setUpCode(code.substring(0, level - 1) + TWO);
            /*右*/
            if (cell.getX() + step < maxL) {
                double x = cell.getX() + step;
                double y = cell.getY();
                StringBuilder rtCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, rtCode, level);
                adjoinCode.setRtCode(String.valueOf(rtCode));
            }
            /*下*/
            if (cell.getY() - step > minL) {
                double x = cell.getX();
                double y = cell.getY() - step;
                StringBuilder dnCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, dnCode, level);
                adjoinCode.setDnCode(String.valueOf(dnCode));
            }
        }
        return adjoinCode;
    }

    /*获取步长*/
    public double getStep(int level) {
        double step = maxL;
        for (int i = 1; i < level; i++) {
            step = step / 2;
        }
        return step;
    }

    /*多多边形编码*/
    public List<String> multiPolygonCode(MultiPolygon multiPolygon, int level) throws ParseException {
        ArrayList<String> list = new ArrayList<>();
        int numGeometries = multiPolygon.getNumGeometries();
        boolean isMultiPolygon = false;
        for (int i = 0; i < numGeometries; i++) {
            Geometry geometryN = multiPolygon.getGeometryN(i);
            List<String> strings = faceCode((Polygon) geometryN, level);
            if (isMultiPolygon) {
                list.removeAll(strings);
            } else {
                list.addAll(strings);
            }
            isMultiPolygon = true;
        }
        aggre(list, level);
        return list;
    }

    /*多点编码*/
    public List<String> multiPointCode(MultiPoint multiPoint, int level) {
        ArrayList<String> list = new ArrayList<>();
        Coordinate[] coordinates = multiPoint.getCoordinates();
        for (Coordinate coordinate : coordinates) {
            double x = coordinate.getX();
            double y = coordinate.getY();
            list.add(coordinateCode(x, y, level));
        }
        aggre(list, level);
        return list;
    }

    /*多线编码*/
    public List<String> multiLineStringCode(MultiLineString multiLineString, int level) throws ParseException {
        ArrayList<String> list = new ArrayList<>();
        int numGeometries = multiLineString.getNumGeometries();
        for (int i = 0; i < numGeometries; i++) {
            Geometry geometryN = multiLineString.getGeometryN(i);
            lineCode((LineString) geometryN, level);
        }
        aggre(list, level);
        return list;
    }

    /*经纬度编码*/
    public String coordinateCode(double x, double y, int level) {
        StringBuilder code = new StringBuilder();
        code(x, y, minL, maxL, minL, maxL, code, level);
        return String.valueOf(code);
    }

    /*点编码*/
    public List<String> pointCode(Point point, int level) {
        ArrayList<String> list = new ArrayList<>();
        double x = point.getX();
        double y = point.getY();
        StringBuilder code = new StringBuilder();
        code(x, y, minL, maxL, minL, maxL, code, level);
        list.add(String.valueOf(code));
        return list;
    }

    /*编码转顶角点
     * 1--|--2
     * ---|---
     * 3--|--4
     * */
    public Cell code2Kdj(String code) {
        double x3 = minL, y3 = minL, x2 = maxL, y2 = maxL;
        int length = code.length();
        for (int i = 0; i < length; i++) {
            String substring = code.substring(i, i + 1);
            switch (substring) {
                case ONE -> {
                    x2 = (x2 + x3) / 2;
                    y3 = (y2 + y3) / 2;
                }
                case TWO -> {
                    x3 = (x2 + x3) / 2;
                    y3 = (y2 + y3) / 2;
                }
                case THREE -> {
                    x2 = (x2 + x3) / 2;
                    y2 = (y2 + y3) / 2;
                }
                case FOUR -> {
                    y2 = (y2 + y3) / 2;
                    x3 = (x2 + x3) / 2;
                }
            }
        }
        return new Cell(x3, y3);

    }

    /*线段编码*/
    public List<String> lineCode(LineString lineString, int level) throws ParseException {
        DJ dj = getLineStringBoundary(lineString);
        double minX = dj.minX;
        double minY = dj.minY;
        double maxX = dj.maxX;
        double maxY = dj.maxY;
        StringBuilder zxCode = new StringBuilder();
        code(minX, minY, minL, maxL, minL, maxL, zxCode, level);
        double step = getStep(level);
        Cell zxcell = code2Kdj(String.valueOf(zxCode));
        StringBuilder ysCode = new StringBuilder();
        code(maxX, maxY, minL, maxL, minL, maxL, ysCode, level);
        Cell yscell = code2Kdj(String.valueOf(ysCode));
        double y3 = zxcell.getY();
        double x3 = zxcell.getX();
        double m = (yscell.getX() - x3) / step;
        double n = (yscell.getY() - y3) / step;
        List<Cell> list = new ArrayList<>();
        for (int x = 0; x < m; x++) {
            for (int y = 0; y < n; y++) {
                Cell target = new Cell();
                target.setX(x3 + step * x);
                target.setY(y3 + step * y);
//                判断cell4顶点是否在polygon中
                if (!disjoint(lineString, target, step)) {
                    target.setX(target.getX() + step / 2);
                    target.setY(target.getY() + step / 2);
                    list.add(target);
                }
            }
        }
        ArrayList<String> retn = new ArrayList<>();
        if (m == 0 && n == 0 && zxCode.equals(ysCode)) {
            retn.add(String.valueOf(zxCode));
        }
        for (Cell cell : list) {
            StringBuilder tmp = new StringBuilder();
            code(cell.getX(), cell.getY(), minL, maxL, minL, maxL, tmp, level);
            retn.add(String.valueOf(tmp));
        }
        aggre(retn, level);
        return retn;
    }

    /*面聚合编码*/
    /*public List<String> faceAggreCode(Polygon polygon, int level) throws ParseException {
        List<String> strings = faceCode(polygon, level).stream().sorted().toList();
        ArrayList<String> list = new ArrayList<>();
        list.addAll(strings);
        aggre(list, 0);
        return list.stream().sorted().toList();
    }*/

    /*聚合*/
    public void aggre(ArrayList<String> sorted, int flag) {
        for (int i = 0; i < sorted.size(); i++) {
            int length = sorted.get(i).length();
            String prefix = sorted.get(i).substring(0, length - 1);
            String s1 = prefix + ONE;
            String s2 = prefix + TWO;
            String s3 = prefix + THREE;
            String s4 = prefix + FOUR;
            if (sorted.contains(s1) && sorted.contains(s2) && sorted.contains(s3) && sorted.contains(s4)) {
                flag = 1;
                sorted.remove(s1);
                sorted.remove(s2);
                sorted.remove(s3);
                sorted.remove(s4);
                if (!sorted.contains(prefix)) {
                    sorted.add(prefix);
                }
            }
        }
        if (flag > 0) {
            aggre(sorted, 0);
        }

    }

    /*面编码无聚合*/
    public List<String> faceCode(Polygon polygon, int level) throws ParseException {
        DJ dj = getPolygonBoundary(polygon);
        double minX = dj.minX;
        double minY = dj.minY;
        double maxX = dj.maxX;
        double maxY = dj.maxY;
        StringBuilder zxCode = new StringBuilder();
        code(minX, minY, minL, maxL, minL, maxL, zxCode, level);
        double step = getStep(level);
        Cell zxcell = code2Kdj(String.valueOf(zxCode));
        StringBuilder ysCode = new StringBuilder();
        code(maxX, maxY, minL, maxL, minL, maxL, ysCode, level);
        Cell yscell = code2Kdj(String.valueOf(ysCode));
        List<Cell> list = new ArrayList<>();
        double m = (yscell.getX() - zxcell.getX()) / step;
        double n = (yscell.getY() - zxcell.getY()) / step;
        for (int x = 0; x < m; x++) {
            for (int y = 0; y < n; y++) {
                Cell target = new Cell();
                target.setY(zxcell.getY() + step * y);
                target.setX(zxcell.getX() + step * x);
//                判断cell4顶点是否在polygon中
                if (!disjoint(polygon, target, step)) {
                    target.setX(target.getX() + step / 2);
                    target.setY(target.getY() + step / 2);
                    list.add(target);
                }
            }
        }
        ArrayList<String> retn = new ArrayList<>();
        if (list.isEmpty()) {
            retn.add(String.valueOf(zxCode));
        }
        for (Cell cell : list) {
            StringBuilder tmp = new StringBuilder();
            code(cell.getX(), cell.getY(), minL, maxL, minL, maxL, tmp, level);
            retn.add(String.valueOf(tmp));
        }
        aggre(retn, 0);
        return retn.stream().sorted().toList();
    }

    /*矢量边界是否在面中*/
    private boolean disjoint(Geometry geometry, Cell target, double step) throws ParseException {
        Polygon cell = getPolygon("POLYGON((" + target.getX() / k + " " + target.getY() / k + "," + (target.getX() + step) / k + " " + target.getY() / k + "," + (target.getX() + step) / k + " " + (target.getY() + step) / k + "," + target.getX() / k + " " + (target.getY() + step) / k + "," + target.getX() / k + " " + target.getY() / k + "))");
        return geometry.disjoint(cell);
    }

    /*获取面边界*/
    public DJ getPolygonBoundary(Polygon polygon) {
        Coordinate[] coordinates = polygon.getBoundary().getCoordinates();
        double minX = coordinates[0].x;
        double minY = coordinates[0].y;
        double maxX = coordinates[0].x;
        double maxY = coordinates[0].y;

        for (Coordinate coordinate : coordinates) {
            if (coordinate.x < minX) minX = coordinate.x;
            if (coordinate.y < minY) minY = coordinate.y;
            if (coordinate.x > maxX) maxX = coordinate.x;
            if (coordinate.y > maxY) maxY = coordinate.y;
        }

        return new DJ(minX * k, minY * k, maxX * k, maxY * k);

    }

    /*获取线边界*/
    public DJ getLineStringBoundary(LineString lineString) {

        Coordinate[] coordinates = lineString.getPointN(0).getCoordinates();
        double minX = coordinates[0].x;
        double minY = coordinates[0].y;
        double maxX = coordinates[0].x;
        double maxY = coordinates[0].y;
        for (int i = 0; i < lineString.getNumPoints(); i++) {
            Point pointN = lineString.getPointN(i);
            double x = pointN.getX();
            if (pointN.getX() < minX) minX = pointN.getX();
            if (pointN.getY() < minY) minY = pointN.getY();
            if (pointN.getX() > maxX) maxX = pointN.getX();
            if (pointN.getY() > maxY) maxY = pointN.getY();
        }

        return new DJ(minX * k, minY * k, maxX * k, maxY * k);

    }

    record DJ(double minX, double minY, double maxX, double maxY) {
    }


    /*编码
     * 1---2
     *   |
     * 3---4
     */
    public void code(double lng, double lat, double minLng, double maxLng, double minLat, double maxLat, StringBuilder code, int level) {
        double midLng = (minLng + maxLng) / 2;
        double midLat = (minLat + maxLat) / 2;
        if (lng < midLng && lat >= midLat) {
            code.append(ONE);
            maxLng = midLng;
            minLat = midLat;
        }
        if (lng >= midLng && lat >= midLat) {
            code.append(TWO);
            minLng = midLng;
            minLat = midLat;
        }
        if (lng < midLng && lat < midLat) {
            code.append(THREE);
            maxLng = midLng;
            maxLat = midLat;
        }
        if (lng >= midLng && lat < midLat) {
            code.append(FOUR);
            minLng = midLng;
            maxLat = midLat;
        }
        if (code.length() < level) {
            code(lng, lat, minLng, maxLng, minLat, maxLat, code, level);
        }
    }


    /**
     * 获取矢量
     * POINT(6 10) //点
     * LINESTRING(3 4,10 50,20 25)//线
     * POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)) //面
     * MULTIPOINT(3.5 5.6, 4.8 10.5)//多点
     * MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))//多线
     * MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)),((6 3,9 2,9 4,6 3)))//多面
     * GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))//复杂几何
     * POINT EMPTY
     * MULTIPOLYGON EMPTY
     */
    public Point getPoint(String wkt) throws ParseException {
        return (Point) getGeometry(wkt);
    }

    public Polygon getPolygon(String wkt) throws ParseException {
        return (Polygon) getGeometry(wkt);
    }

    public MultiPoint getMultiPoint(String wkt) throws ParseException {
        return (MultiPoint) getGeometry(wkt);
    }

    public MultiLineString getMultiLineString(String wkt) throws ParseException {
        return (MultiLineString) getGeometry(wkt);
    }

    public MultiPolygon getMultiPolygon(String wkt) throws ParseException {
        return (MultiPolygon) getGeometry(wkt);
    }

    public LineString getLineString(String wkt) throws ParseException {
        return (LineString) getGeometry(wkt);
    }


    public Geometry getGeometry(String wkt) throws ParseException {
        /*produce*/
        //        return wktReader.read(wkt);
        /*test*/
        return new WKTReader(new GeometryFactory()).read(wkt);
    }

    /*网格集关系计算 交并差*/
    public CodesNexus codesNexus(List<String> A, List<String> B) {
        List<String> nexus = new ArrayList<>();
        String relation = "";
        for (String a : A) {
            for (String b : B) {
                if (a.equals(b)) {
                    nexus.add(a);
                } else if (a.startsWith(b)) {
                    nexus.add(a);
                } else if (b.startsWith(a)) {
                    nexus.add(b);
                }
            }
        }
        if (nexus.isEmpty()) {
            relation = APART;
        } else if (nexus.size() == A.size()) {
            relation = AinB;
        } else if (nexus.size() == B.size()) {
            relation = BinA;
        } else {
            relation = INTERSECT;
        }
        return new CodesNexus(relation, nexus);
    }

    /*==========================局部剖分=========================================*/

    Map<Integer, Integer> levelStep = new HashMap<>();

    {
        levelStep.put(26, 1);
        levelStep.put(25, 2);
        levelStep.put(24, 4);
        levelStep.put(23, 8);
        levelStep.put(22, 16);
        levelStep.put(21, 32);
        levelStep.put(20, 64);
        levelStep.put(19, 128);
        levelStep.put(18, 256);
        levelStep.put(17, 512);
        levelStep.put(16, 1024);
        levelStep.put(15, 2048);
        levelStep.put(14, 4096);
        levelStep.put(13, 8192);
        levelStep.put(12, 16384);
        levelStep.put(11, 32768);
        levelStep.put(10, 65536);
    }

    public AdjoinCode get4Padjoin(String code) {
        AdjoinCode adjoinCode = new AdjoinCode();
        int level = code.length();
        Cell cell = code2Kdj(code);
        double step = getStep(level);
//        邻格
        if (code.endsWith(ONE)) {
            adjoinCode.setRtCode(code.substring(0, level - 1) + TWO);
            adjoinCode.setDnCode(code.substring(0, level - 1) + THREE);
            /*上*/
            if (cell.getY() + step < maxL) {
                double x = cell.getX();
                double y = cell.getY() + step;
                StringBuilder upCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, upCode, level);
                adjoinCode.setUpCode(String.valueOf(upCode));
            }
            /*左*/
            if (cell.getX() - step > minL) {
                double x = cell.getX() - step;
                double y = cell.getY();
                StringBuilder lfCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, lfCode, level);
                adjoinCode.setLfCode(String.valueOf(lfCode));
            }
        } else if (code.endsWith(TWO)) {
            adjoinCode.setLfCode(code.substring(0, level - 1) + ONE);
            adjoinCode.setDnCode(code.substring(0, level - 1) + FOUR);
            /*上*/
            if (cell.getY() + step < maxL) {
                double x = cell.getX();
                double y = cell.getY() + step;
                StringBuilder upCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, upCode, level);
                adjoinCode.setUpCode(String.valueOf(upCode));
            }
            /*右*/
            if (cell.getX() + step < maxL) {
                double x = cell.getX() + step;
                double y = cell.getY();
                StringBuilder rtCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, rtCode, level);
                adjoinCode.setRtCode(String.valueOf(rtCode));
            }
        } else if (code.endsWith(THREE)) {
            adjoinCode.setUpCode(code.substring(0, level - 1) + ONE);
            adjoinCode.setRtCode(code.substring(0, level - 1) + FOUR);
            /*左*/
            if (cell.getX() - step > minL) {
                double x = cell.getX() - step;
                double y = cell.getY();
                StringBuilder lfCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, lfCode, level);
                adjoinCode.setLfCode(String.valueOf(lfCode));
            }
            /*下*/
            if (cell.getY() - step > minL) {
                double x = cell.getX();
                double y = cell.getY() - step;
                StringBuilder dnCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, dnCode, level);
                adjoinCode.setDnCode(String.valueOf(dnCode));
            }
        } else if (code.endsWith(FOUR)) {
            adjoinCode.setLfCode(code.substring(0, level - 1) + THREE);
            adjoinCode.setUpCode(code.substring(0, level - 1) + TWO);
            /*右*/
            if (cell.getX() + step < maxL) {
                double x = cell.getX() + step;
                double y = cell.getY();
                StringBuilder rtCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, rtCode, level);
                adjoinCode.setRtCode(String.valueOf(rtCode));
            }
            /*下*/
            if (cell.getY() - step > minL) {
                double x = cell.getX();
                double y = cell.getY() - step;
                StringBuilder dnCode = new StringBuilder();
                code(x, y, minL, maxL, minL, maxL, dnCode, level);
                adjoinCode.setDnCode(String.valueOf(dnCode));
            }
        }
        return adjoinCode;
    }

    /*获取最大边长 todo*/
    double getRange(double maxLenth) {
        double result = 1;
        while (result < maxLenth) {
            result *= 2;
        }
        return result;
    }


    public void partCode(double len, double wid, double minLen,
                         double maxLen, double minWid, double maxWid,
                         StringBuilder code, int step, double midLen, double midWid) {
        midLen = (minLen + maxLen) / 2;
        midWid = (minWid + maxWid) / 2;
        if (len < midLen && wid >= midWid) {
            code.append(ONE);
            maxLen = midLen;
            minWid = midWid;
        }
        if (len >= midLen && wid >= midWid) {
            code.append(TWO);
            minLen = midLen;
            minWid = midWid;
        }
        if (len < midLen && wid < midWid) {
            code.append(THREE);
            maxLen = midLen;
            maxWid = midWid;
        }
        if (len >= midLen && wid < midWid) {
            code.append(FOUR);
            minLen = midLen;
            maxWid = midWid;
        }
        if ((maxLen - minLen) > step) {
            partCode(len, wid, minLen, maxLen, minWid, maxWid, code, step, midLen, midWid);
        }
    }
}
